package com.cy.caffeine;

import com.github.benmanes.caffeine.cache.*;
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * @author yue.cao
 * @since 11-05-2020
 */
@Slf4j
public class DemoWithCaffeine {

	private String CACHE_KEY = "IamKey";


	public static void main(String[] args) throws InterruptedException {
		long START = System.currentTimeMillis();
		int size = args[0] != null ? Integer.parseInt(args[0]) : 20000000;
		boolean isInitSize = args.length == 3 && args[2] != null;

		String type = args[1];
		if (type.equals("a")) {
			Caffeine<Object, Object> objectObjectCaffeine = Caffeine.newBuilder();
			objectObjectCaffeine.maximumSize(size);
			if (isInitSize) {
				System.out.println("init-->" + isInitSize);
				objectObjectCaffeine.initialCapacity(size);
			}
			Cache<String, Boolean> map = objectObjectCaffeine.build();
			for (int i = 0; i < size; i++) {
				map.put(i + "", true);
			}

			System.out.println(map.getIfPresent("0"));
			System.out.println(map.getIfPresent((size - 1) + ""));
			System.out.println(map.getIfPresent(size + ""));
		} else if (type.equals("b")) {
			CacheBuilder<Object, Object> objectObjectCacheBuilder = CacheBuilder.newBuilder();
			objectObjectCacheBuilder.maximumSize(size);
			if (isInitSize) {
				System.out.println("init-->" + isInitSize);
				objectObjectCacheBuilder.initialCapacity(size);
			}
			com.google.common.cache.Cache<String, Boolean> map = objectObjectCacheBuilder.build();
			for (int i = 0; i < size; i++) {
				map.put(i + "", true);
			}
			System.out.println(map.getIfPresent("0"));
			System.out.println(map.getIfPresent((size - 1) + ""));
			System.out.println(map.getIfPresent(size + ""));
		}
		System.out.println((System.currentTimeMillis() - START) / 1000);
	}

	@Test
	public void testMain1() {
		final Integer[] num = {0};
		LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
				.maximumSize(50)
				.expireAfterAccess(1, TimeUnit.MINUTES)
				.recordStats()
				.removalListener(new RemovalListener<Object, Object>() {
					@Override
					public void onRemoval(@Nullable Object key, @Nullable Object value, RemovalCause cause) {
						log.error("removalListener==> key:[{}] value:[{}] case:[{}]", key, value, cause);
					}
				})
				.build(new CacheLoader<String, String>() {
					@Override
					public String load(@NonNull String key) throws Exception {
						num[0]++;
						return "cy" + key;
					}
				});

		for (int i = 0; i < 101; i++) {
			loadingCache.get(i + "");
		}

		for (int i = 0; i < 101; i++) {
			Assert.assertEquals(loadingCache.get(i + ""), "cy" + i);
		}


		System.out.println(Arrays.toString(num));
	}

	@Test
	public void tl1() {
		LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
				.maximumSize(50)
				.expireAfterAccess(1, TimeUnit.MINUTES)
				.recordStats()

				.build(new CacheLoader<String, String>() {
					@Override
					public String load(@NonNull String key) throws Exception {
						System.out.println("---------------");
						if (1 == 1) {
							throw new FlinkMsgPassException("");
						}
						return key.toString();
					}
				});

		try {
			loadingCache.get("11");
		} catch (FlinkMsgPassException e) {
			System.out.println("cy");
			e.printStackTrace();
		} catch (Exception e) {
			System.out.println("cy2");
			e.printStackTrace();
		}
	}

	@Test
	public void testMain2() {
		int size = 10000000;

		com.google.common.cache.Cache<String, Boolean> map = CacheBuilder.newBuilder()
				.maximumSize(size)
				.build();
		for (int i = 0; i < size; i++) {
			map.put(i + "", true);
		}
		System.out.println(map.getIfPresent("0"));
		System.out.println(map.getIfPresent((size - 1) + ""));
		System.out.println(map.getIfPresent(size + ""));
	}


	@Test(description = "手动加载(Manual) 就是人为的根据判断然后加载数据")
	public void t1() {
		Cache<String, Object> manualCache =
				Caffeine.newBuilder()
						.maximumSize(10)
						.build();
		// 根据key查询一个缓存，如果没有返回NULL
		Object cacheValue = manualCache.getIfPresent(CACHE_KEY);

		Assert.assertNull(cacheValue);
		// 根据Key查询一个缓存，如果没有调用createExpensiveGraph方法，并将返回值保存到缓存。
		// 如果该方法返回Null则manualCache.get返回null，如果该方法抛出异常则manualCache.get抛出异常
		cacheValue = manualCache.get(CACHE_KEY, new Function<String, Object>() {
			@Override
			public Object apply(String s) {
				return "cy";
			}
		});
		Assert.assertEquals(cacheValue, "cy");
		cacheValue = manualCache.getIfPresent(CACHE_KEY);
		Assert.assertEquals(cacheValue, "cy");
		// 将一个值放入缓存，如果以前有值就覆盖以前的值
		manualCache.put(CACHE_KEY, "cy2");
		Assert.assertEquals(manualCache.getIfPresent(CACHE_KEY), "cy2");
		Assert.assertEquals(manualCache.estimatedSize(), 1);
		// 删除一个缓存
		manualCache.invalidate(CACHE_KEY);
		Assert.assertEquals(manualCache.estimatedSize(), 0);
	}

	@Test(description = "同步加载 Loading")
	public void t2() {
		LoadingCache<String, Object> loadingCache = Caffeine.newBuilder()
				.maximumSize(10)
				.expireAfterWrite(10, TimeUnit.MINUTES)
				.build(new CacheLoader<String, Object>() {
					@Override
					public Object load(@NonNull String key) throws Exception {
						return "cy";
					}
				});

		// 采用同步方式去获取一个缓存和上面的手动方式是一个原理。在build Cache的时候会提供一个createExpensiveGraph函数。
		// 查询并在缺失的情况下使用同步的方式来构建一个缓存
		Object cacheValue = loadingCache.get(CACHE_KEY);
		Assert.assertEquals(cacheValue, "cy");
		// 获取组key的值返回一个Map
		List<String> keys = new ArrayList<>();
		keys.add(CACHE_KEY);
		Map<String, Object> cacheMap = loadingCache.getAll(keys);
		System.out.println(cacheMap);
	}


	@Test(description = "异步加载")
	public void t3() {
		AsyncLoadingCache<String, Object> asyncLoadingCache = Caffeine.newBuilder()
				.maximumSize(10_000)
				.expireAfterWrite(10, TimeUnit.MINUTES)
				// Either: Build with a synchronous computation that is wrapped as asynchronous
				.buildAsync(new CacheLoader<String, Object>() {
					@Override
					public Object load(@NonNull String key) throws Exception {
						return "cy";
					}
				});
		// Or: Build with a asynchronous computation that returns a future
		// .buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));

		// 查询并在缺失的情况下使用异步的方式来构建缓存
		CompletableFuture<Object> graph = asyncLoadingCache.get(CACHE_KEY);
		// 查询一组缓存并在缺失的情况下使用异步的方式来构建缓存
		List<String> keys = new ArrayList<>();
		keys.add(CACHE_KEY);
		CompletableFuture<Map<String, Object>> graphs = asyncLoadingCache.getAll(keys);
		// 异步转同步
		LoadingCache<String, Object> synchronous = asyncLoadingCache.synchronous();
	}

	@Test(description = "驱逐策略")
	public void t4() {
		Cache<String, Object> manualCache = Caffeine.newBuilder()
				.expireAfterWrite(10, TimeUnit.MINUTES)
				.build();
	}

	@Test(description = "删除")
	public void t5() {
		Cache<String, Object> manualCache = Caffeine.newBuilder()
				.expireAfterWrite(10, TimeUnit.MINUTES)
				.build();
		// individual cacheKey
		Object cacheKey = null;
		manualCache.invalidate(cacheKey);
		// bulk keys
		Iterable<?> cacheKeys = null;
		manualCache.invalidateAll(cacheKeys);
		// all keys
		manualCache.invalidateAll();
	}

	@Test(description = "删除监听器")
	public void t6() throws InterruptedException {
		Cache<String, Object> manualCache = Caffeine.newBuilder()
				.maximumSize(10)
				.expireAfterWrite(5, TimeUnit.MINUTES)
				.removalListener(new RemovalListener<Object, Object>() {
					@Override
					public void onRemoval(@Nullable Object key, @Nullable Object value, RemovalCause cause) {
						log.error("removalListener==> key:[{}] value:[{}] case:[{}]", key, value, cause);
					}
				})
				.recordStats()
				.build();

		for (int i = 0; i < 20; i++) {
			manualCache.put(i + "", i);
			TimeUnit.SECONDS.sleep(1);
		}

	}


}
